package u1;

import static java.lang.System.*;
import static java.lang.Math.*;
import static u1.Util.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Build a dot file (www.graphviz.org) for visualising a (small!) graph. Reads
 * adjacency list on stdin. Prints the dot file on stdout. Options (all of which
 * are optional):
 * 
 * <ul>
 * <li>-ffilter_file: file containing page numbers, delimited by whitespace. If
 * omitted, all nodes from the adjacency list are printed.</li>
 * <li>-ttitles_file: file containing titles, one per line. Must be UTF-8
 * encoded. If omitted, nodes are not labelled.</li>
 * <li>-rredirect_file: file containing page numbers of nodes that are redirects
 * and the numbers of the nodes they redirect to, delimited by whitespace.
 * Redirect nodes are marked with dotted borders. If omitted, redirects are not
 * marked specially on the graph.</li>
 * <li>-wweight_file: file containing page_number weight pairs, delimited by
 * whitespace. Page numbers are integral and weights are read as doubles. A
 * page's weight is used to size the font used to display its name. If omitted,
 * weight information is not included on the graph. Typically these weights will
 * be page ranks.
 * </ul>
 */
public class PrettyDot {
  private static List<String> titles = null;
  private static Set<Integer> redirects = null;
  private static Set<Integer> filter = null;
  private static Map<Integer, Double> weights = null;

  public static void main(String[] args) throws IOException {
    File filterFile = null;
    File titlesFile = null;
    File redirectFile = null;
    File weightFile = null;
    for (int i = 0; i < args.length; ++i) {
      if (args[i].length() < 2) {
        trace("Unrecognized option %s. Ignoring.", args[i]);
        continue;
      }
      if (args[i].startsWith("-f"))
        filterFile = new File(args[i].substring(2));
      else if (args[i].startsWith("-t"))
        titlesFile = new File(args[i].substring(2));
      else if (args[i].startsWith("-r"))
        redirectFile = new File(args[i].substring(2));
      else if (args[i].startsWith("-w"))
        weightFile = new File(args[i].substring(2));
      else {
        trace("Unrecognized option %s. Ignoring.", args[i]);
      }
    }

    /*
     * Read filter.
     */
    if (filterFile == null)
      trace("Using all nodes (no filter).");
    else {
      trace("Reading filter file %s...", filterFile);
      InputStream is = new FileInputStream(filterFile);
      List<Integer> filterList = new ArrayList<Integer>();
      readIntegers(filterList, is);
      is.close();
      filter = new HashSet<Integer>(filterList);
      trace("Will include %d nodes.", filter.size());
    }

    /*
     * Read titles.
     */
    if (titlesFile == null)
      trace("Not using titles.");
    else {
      trace("Reading titles from %s...", titlesFile);
      BufferedReader reader = new BufferedReader(new InputStreamReader(
          new FileInputStream(titlesFile), "UTF-8"));
      titles = new ArrayList<String>();
      readLines(titles, reader);
      reader.close();
      trace("Read %d titles.", titles.size());
    }

    /*
     * Read redirects.
     */
    if (redirectFile == null)
      trace("Not using redirects.");
    else {
      trace("Reading redirects from %s...", redirectFile);
      InputStream is = new FileInputStream(redirectFile);
      List<Integer> redirectList = new ArrayList<Integer>();
      readIntegers(redirectList, is);
      is.close();
      // Every second entry is actually a redirect destination.
      assert (redirectList.size() % 2 == 0);
      List<Integer> redirectListEvens = new ArrayList<Integer>(redirectList
          .size() / 2);
      for (int i = 0; i < redirectList.size(); i += 2)
        redirectListEvens.add(redirectList.get(i));
      redirects = new HashSet<Integer>(redirectListEvens);
      trace("Read %d redirects.", redirects.size());
    }

    /*
     * Read page weights.
     */
    if (weightFile == null)
      trace("Not using weights.");
    else {
      trace("Reading weights from %s...", weightFile);
      weights = new HashMap<Integer, Double>();
      readIntDoubleMap(weights, weightFile, filter);
      trace("Read %d ranks.", weights.size());

      /*
       * Normalise ranks so they correspond to font sizes. We want to represent
       * them as font sizes in some range.
       */
      final double minFont = 8;
      final double maxFont = 20;
      double maxRank = -1;
      for (Double r : weights.values())
        if (r > maxRank)
          maxRank = r;
      if (maxRank <= 0.0) {
        trace("Invalid ranks: maximum rank is non-positive.");
        exit(-1);
      } else {
        for (Map.Entry<Integer, Double> e : weights.entrySet()) {
          double v = max(abs(e.getValue()), Double.MIN_VALUE);
          e.setValue(minFont + (maxFont - minFont) * (v / maxRank));
        }
      }
    }

    /*
     * Read input adjacency list.
     */
    trace("Reading adjacency list from stdin.");
    final Map<String, String> nodeAttribs = new HashMap<String, String>();
    AdjReader adjReader = new AdjReader() {
      @Override
      public void readLinks(Integer source, Collection<Integer> targets) {
        // Ignore if not in filter (if using filter).
        if (filter != null && !filter.contains(source))
          return;

        // Recover properties.
        nodeAttribs.clear();
        if (titles != null) {
          String title = titles.get(source).replace('"', '\'');
          nodeAttribs.put("label", String.format("\"%s\"", title));
        }
        if (weights != null) {
          nodeAttribs.put("fontsize", String
              .format("%.0f", weights.get(source)));
        }
        if (redirects != null && redirects.contains(source)) {
          nodeAttribs.put("style", "dotted");
        }

        // Print node.
        out.print(source.toString());
        if (nodeAttribs.size() > 0) {
          out.print(" [");
          boolean first = true;
          for (Map.Entry<String, String> e : nodeAttribs.entrySet()) {
            if (first)
              first = false;
            else
              out.print(",");
            out.print(e.getKey());
            out.print("=");
            out.print(e.getValue());
          }
          out.print("]");
        }
        out.println();

        // Print links.
        for (Integer target : targets) {
          if (filter != null && !filter.contains(target))
            continue;
          out.print(source);
          out.print("->");
          out.println(target);
        }
      }
    };

    out.println("digraph G {");
    adjReader.read(in);
    out.println("}");

    trace("Read %d pages (%d links) from adjacency list.", adjReader
        .getPageIndex(), adjReader.getLinkCount());
  }
}
/*
* Copyright (c) 2009 John Lees-Miller
* 
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
* 
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

